﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using QstkCore.Util;

namespace QstkCore.DataFrame
{
    public interface IDataFrame : IEnumerable<double>
    {
        IList<string> ColumnNames { get; set; }
        int Nrow { get; }
        int Ncol { get; }
        double this[int row, int col] { get; set; }
        TimeSeriesDataFrame this[string col] { get; }
    }

    public class TimeSeriesDataFrame : IDataFrame
    {
        private Dictionary<string, int> _colNames;
        private Dictionary<DateTime, int> _rowNames;

        private readonly int nrow;
        private readonly int ncol;
        private double? scalar;
        private double[] data;
        private IList<ArraySegment<double>> rowSegments;

        public TimeSeriesDataFrame(int rows, int columns)
        {
            nrow = rows;
            ncol = columns;

            data = new double[rows*columns];
        }

        public TimeSeriesDataFrame(int rows, int columns, IList<ArraySegment<double>> rowSegments)
        {
            nrow = rows;
            ncol = columns;
            this.rowSegments = rowSegments;
        }

        // Scalar DataFrame
        public TimeSeriesDataFrame(double value)
        {
            scalar = value;
        }

        public TimeSeriesDataFrame(TimeSeriesDataFrame df)
        {
            _colNames = df._colNames;
            _rowNames = df._rowNames;

            nrow = df.nrow;
            ncol = df.ncol;
            scalar = df.scalar;
            data = df.data;
            rowSegments = df.rowSegments;
        }

        public void Dispose()
        {
            // Set large fields to null.
            _colNames = null;
            _rowNames = null;
            data = null;
            rowSegments = null;
        }

        public int Nrow
        {
            get { return nrow; }
        }

        public int Ncol
        {
            get { return ncol; }
        }


        public IList<string> ColumnNames
        {
            get { return _colNames.Keys.ToList(); }
            set { _colNames = value.ToDictionary(x => x, value.IndexOf); }
        }

        public List<DateTime> TimeSeries
        {
            get { return _rowNames.Keys.ToList(); }
            set { _rowNames = value.ToDictionary(x => x, value.IndexOf); }
        }

        public void SetRow(int row, double[] rowData)
        {
            Debug.Assert(row < nrow);

            for (int i = 0; i < ncol; i++)
                data[_Index(row, i)] = rowData[i];
        }

        private int _Index(int row, int i)
        {
            return (row*ncol)+i;
        }

        public TimeSeriesDataFrame Slice(IntRange rows, IntRange cols)
        {
            int rowstart = 0, rowend = nrow;
            if (rows != null)
            {
                rowstart = rows.Minimum.GetValueOrDefault();
                rowend = rows.Maximum.GetValueOrDefault(nrow);                
            }

            int colstart = 0, colend = ncol;
            if (cols != null)
            {
                colstart = cols.Minimum.GetValueOrDefault();
                colend = cols.Maximum.GetValueOrDefault(ncol);
            }

            var rowsList = new List<ArraySegment<double>>(rowend-rowstart);

            for (int i = rowstart; i < nrow && i < rowend; i++)
                rowsList.Add(new ArraySegment<double>(data, _Index(i, colstart), colend-colstart));

            var slice = new TimeSeriesDataFrame(rowend - rowstart, colend - colstart, rowsList);

            return slice;
        }

        public TimeSeriesDataFrame Head(int rows)
        {
            return Slice(new IntRange(0, rows), null);
        }

        public TimeSeriesDataFrame Tail(int rows)
        {
            return Slice(new IntRange(Nrow-rows, Nrow), null);
        }

        public TimeSeriesDataFrame FromIEnumerable(IEnumerable<double> values)
        {
            var df = new TimeSeriesDataFrame(1, values.Count());
            df.data = (double[]) values.ToArray();
            return df;
        }

        public static implicit operator TimeSeriesDataFrame(double[] values)
        {
            var df = new TimeSeriesDataFrame(1, values.Length);
            df.data = (double[])values.Clone();
            return df;
        }

        public static implicit operator TimeSeriesDataFrame(double[,] values)
        {
            var df = new TimeSeriesDataFrame(values.GetLength(0), values.GetLength(1));
            df.data = (double[])values.Clone();
            return df;
        }

        public static TimeSeriesDataFrame operator +(TimeSeriesDataFrame c1, TimeSeriesDataFrame c2)
        {
            Debug.Assert(c1.Nrow == c2.Nrow);
            Debug.Assert(c1.Ncol == c2.Ncol);

            var df = new TimeSeriesDataFrame(c1.Nrow, c1.Ncol);

            // TODO - Parallelize ?
            for (int i = 0; i < c1.Nrow; i++)
                for (int j = 0; j < c2.Ncol; j++)
                    df[i, j] = c1[i, j] + c2[i, j];

            return df;
        }

        public static TimeSeriesDataFrame operator -(TimeSeriesDataFrame c1, TimeSeriesDataFrame c2)
        {
            Debug.Assert(c1.Nrow == c2.Nrow);
            Debug.Assert(c1.Ncol == c2.Ncol);

            var df = new TimeSeriesDataFrame(c1.Nrow, c1.Ncol);

            // Parallelize ?
            for (int i = 0; i < c1.Nrow; i++)
                for (int j = 0; j < c2.Ncol; j++)
                    df[i, j] = c1[i, j] - c2[i, j];

            return df;
        }

        public static TimeSeriesDataFrame operator *(TimeSeriesDataFrame c1, TimeSeriesDataFrame c2)
        {
            Debug.Assert(c1.Nrow == c2.Nrow);
            Debug.Assert(c1.Ncol == c2.Ncol);

            var df = new TimeSeriesDataFrame(c1.Nrow, c1.Ncol);

            // Parallelize ?
            for (int i = 0; i < c1.Nrow; i++)
                for (int j = 0; j < c2.Ncol; j++)
                    df[i, j] = c1[i, j] * c2[i, j];

            return df;
        }

        public static TimeSeriesDataFrame operator /(TimeSeriesDataFrame c1, TimeSeriesDataFrame c2)
        {
            Debug.Assert(c1.Nrow == c2.Nrow);
            Debug.Assert(c1.Ncol == c2.Ncol);

            var df = new TimeSeriesDataFrame(c1.Nrow, c1.Ncol);

            // Parallelize ?
            for (int i = 0; i < c1.Nrow; i++)
                for (int j = 0; j < c2.Ncol; j++)
                    df[i, j] = c1[i, j] / c2[i, j];

            return df;
        }

        public static TimeSeriesDataFrame operator &(TimeSeriesDataFrame c1, TimeSeriesDataFrame c2)
        {
            throw new NotImplementedException();
        }

        public static TimeSeriesDataFrame operator |(TimeSeriesDataFrame c1, TimeSeriesDataFrame c2)
        {
            throw new NotImplementedException();
        }

        public static TimeSeriesDataFrame operator !(TimeSeriesDataFrame c1)
        {
            throw new NotImplementedException();
        }

        public TimeSeriesDataFrame this[DateTime row]
        {
            get
            {
                int rowIndex = _rowNames[row];

                return Slice(new IntRange(rowIndex, rowIndex+1), new IntRange(0, ncol));
            }
        }

        public TimeSeriesDataFrame this[string col]
        {
            get
            {
                int colIndex = _colNames[col];

                return Slice(new IntRange(0, nrow), new IntRange(colIndex, colIndex+1));
            }
        }

        public double this[int row, int col]
        {
            get
            {
                if(scalar.HasValue)
                {
                    return scalar.Value;
                }
                else if (data != null)
                {
                    return data[_Index(row, col)];
                }
                else if (rowSegments != null)
                {
                    var segment = rowSegments[row];
                    return segment.Array[segment.Offset + col];
                }
                else 
                    throw new InvalidOperationException();
            }
            set
            {
                if (scalar.HasValue)
                {
                    scalar = value;
                }
                else if (data != null)
                {
                    data[_Index(row, col)] = value;
                }
                else if (rowSegments != null)
                {
                    var segment = rowSegments[row];
                    segment.Array[segment.Offset + col] = value;
                }
                else 
                    throw new InvalidOperationException();
            }
        }

        public TimeSeriesDataFrame this[int? row, int? col]
        {
            get
            {
                if (row.HasValue && col.HasValue)
                {
                    return new TimeSeriesDataFrame(this[row.Value, col.Value]);
                }
                return null; // TODO - null range indexing
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public TimeSeriesDataFrame this[DateTime row, string col]
        {
            get
            {
                int rowIndex = _rowNames[row];
                int colIndex = _colNames[col];

                return new TimeSeriesDataFrame(this[rowIndex, colIndex]);
            }
        }

        public TimeSeriesDataFrame this[DateTime row, int? col]
        {
            get
            {
                if (col.HasValue)
                {
                    return this[row, col.Value];
                }
                return null; // TODO - null range indexing
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public TimeSeriesDataFrame this[IntRange rows, IntRange cols]
        {
            get { return Slice(rows, cols); }
        }

        public TimeSeriesDataFrame this[IList<bool> rows, IList<bool> cols]
        {
            get { throw new NotImplementedException(); }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public IEnumerator<double> GetEnumerator()
        {
            if(scalar.HasValue)
            {
                yield return scalar.Value;
            }
            else if(data != null)
            {
                for (int i = 0; i < nrow; i++)
                {
                    for (int j = 0; j < ncol; j++)
                    {
                        double d = data[_Index(i, j)];
                        yield return d;
                    }
                }
            }
            else if (rowSegments != null)
            {
                for (int i = 0; i < nrow; i++)
                {
                    for (int j = 0; j < ncol; j++)
                    {
                        var segment = rowSegments[i];
                        double d = segment.Array[segment.Offset + j];
                        yield return d;
                    }
                }                
            }
        }

        public override string ToString()
        {
            throw new NotImplementedException();            
        }
    }

    public static class DataFrameExtension
    {
        public static IDataFrame ReplaceNanFill(this IDataFrame df, FillDirection method)
        {
            foreach (string stock in df.ColumnNames)
            {
                switch (method)
                {
                    case FillDirection.Forward:
                        ForwardFill(df[stock]);
                        break;
                    case FillDirection.Backward:
                        BackwardFill(df[stock]);
                        break;
                }
            }

            return df;
        }

        private static IDataFrame ForwardFill(this IDataFrame df)
        {
            int lastRow = -1;
            for (int i = 0; i < df.Nrow; i++)
                lastRow = Fill(df, i, lastRow);

            return df;
        }

        private static IDataFrame BackwardFill(this IDataFrame df)
        {
            int lastRow = -1;
            for (int i = df.Nrow - 1; i >= 0; i--)
                lastRow = Fill(df, i, lastRow);

            return df;
        }

        private static int Fill(this IDataFrame df, int currentRow, int previousRow)
        {
            if (previousRow >= 0)
            {
                if (double.IsNaN(df[currentRow,0]))
                    df[currentRow,0] = df[previousRow,0];
                return currentRow;
            }

            return currentRow;
        }

        public static IDataFrame Returnize0<T>(this IDataFrame prices)
        {
            throw new NotImplementedException();
        }

        public static IDataFrame ReadCsv(this StreamReader sr)
        {
            throw new NotImplementedException();            
        }
    }
}
